서론
현재 회사에서 운영중인 여러 서비스 중 신규 피쳐를 테스트해볼 수 있는, 일종의 test-bed 격의 서비스(이하 서비스 H)가 있습니다. H 는 2013년에 출시된 서비스로 개발된 지 상당히 오랜 시간이 지나 현재는 신규 개발보다 운영 및 유지보수에 집중하고 있습니다. H는 python2 언어로 개발되었고 현재도 python2 로 운영중입니다. python3 로 업그레이드를 진행할 법도 하지만 팀 리소스 상의 제약과 여러 이유로 H 는 python2 로 남아있습니다.
최근 긴급 배포 상황에서 CI 파이프라인의 길 실행 시간(약 22분)이 병목이 됐고 이에 대해 근본적인 해결이 필요한 상황이었습니다. 테스트 베드의 역할에 맞게 빠른 테스트와 배포가 필요할 때마다 CI 과정에서 병목은 이상적이지 않았고 무엇보다 python2 코드 파악도 여간 쉬운 일이 아니기 때문에 python3 로 마이그레이션을 할 때가 왔다고 생각했습니다. 장기적인 관점에서 서비스 H를 Python 3로 마이그레이션하는 필요성을 느꼈지만 당장 시행할 수 없는 부분이니 CI 시간부터 단축해보기로 했습니다.
기존 ci 과정의 문제점
현재 H 는 python2.7 버전으로 운영되지만 Python 2.7은 2020년 1월부터 공식 지원이 중단되어 보안 패치나 버그 수정이 더 이상 제공되지 않습니다. H 서비스의 CI 는 3가지 작업으로 나뉘는데 이 작업은 모두 병렬로 실행됩니다. 그럼에도 ci 에 긴 시간이 소요되는 것은 특정 작업에서 오랜 시간이 걸리는 것으로 유추할 수 있었습니다.
현재 CI Pipeline 구성
- CI-Test : 테스트 실행 (메인 병목 지점) - 14m 57s 소요 (가장 최근 빌드 소요시간)
- CI-Build : 프로젝트 빌드 - 2m 48s 소요
- CI-Security : 보안 검사 - 5m 16s 소요
중복되는 ci-test
실제로 긴 병목을 가진 작업은 ci-test 로 평균 11분의 시간을 소요하고 있었습니다. 뿐만 아니라 해당 ci-test 과정은 PR 단계 뿐만 아니라
master merge 후에도 실행되기 때문에, 하나의 PR 에서 마스터 브랜치 머지할 때 동일한 (그리고 긴) ci-test 작업이 두번 실행되어 배포까지 나가는데 시간이 오래걸렸습니다.
아래 이미지에서 마지막 두 항목은 동일한 PR 에서 발생하는 ci-test 과정으로, PR 단계와 마스터 브랜치 merge 이후 단계에서 각각 중복 실행되는 것을 확인할 수 있습니다.
불필요한 패키지 설치
ci-build 와 달리 ci-test 과정은 불필요한 패키지와 소스 빌드 과정이 포함되어 더 긴 시간이 소요됐습니다. 실제 테스트를 실행 시간은 2분 내외였지만 그 외의 리소스 설치 과정이 병목에 원인이었습니다. 이미 공식 버전 지원을 종료한 python2.7 소스 빌드와 PowerShell 다운/설치, 불필요한 시스템 패키지 업그레이드 등 test 의 속도를 늦추는 많은 작업이 ci-test 에 포함돼 있었습니다.
현재 CI-Test 시간 분석
- ci-test: 평균 11분 (메인 병목)
- master merge: merge 후 ci-test 중복 실행
- 전체 CI 시간: +22분 (PR + Master 머지)
CI Test 최적화 전략 및 구현
1. 실행 트리거 최적화
마스터 브랜치 merge 후에는 중복 실행되지 않도록 ci-test 파일에서 트리거를 제한했습니다. 이로써 PR 단계에서 마스터 브랜치로 merge 되기 전에만 ci-test 가 실행되며 merge 이후에는 불필요하게 실행되지 않아 배포 프로세스 속도를 높일 수 있었습니다.
성능 개선
: 전체 배포 시간 50% 단축
// 기존 name: ci-test ... // 개선 name: ci-test on: pull_request: # PR 에서만 실행
2. PowerShell 설치 제거
기존 ci-test 파일에는 powershell 설치 명령어가 존재했습니다. H 서비스의 test 를 수행하려면 python2.7 설치가 선행되어야 하지만 powershell 은 python2.7 테스트 실행에 전혀 필요하지 않았기 때문에 불필요한 의존성이라 판단했습니다. 무엇보다 설치 후에는 어떠한 작업도 수행하지 않았기에 이를 제거하기로 했습니다. 예상컨대 과거에는 powershell 을 사용하여 python 을 실행했지만, 시스템이 진화하면서 powershell 없이도 python 을 실행하게 되면서 미처 지우지 못한 코드가 남아있는 것은 아닌지 생각을 해봅니다.
결과적으로 powershell 설치 과정을 제거함으로 75MB 크기의 패키지 다운로드를 생략하며 테스트 시간을 대폭 줄일 수 있었습니다.
성능 개선
: 56s ➡️ 0s
powershell 이란
- Microsoft가 개발한 태스크 자동화 및 구성 관리 프레임워크로 CLI 와 스크립팅 언어로 자동화를 지원합니다.
3. Python 소스 빌드 제거
Python 2.7의 공식 지원이 종료되어 사전 빌드된 바이너리가 없어 기존 ci-test 과정에서 매번 소스코드를 다운받아 컴파일했습니다. 공식 actions/setup-python이 Python 2.7 지원을 중단하여 MatteoH2O1999/setup-python@v2 액션을 사용중이었으며 해당 액션은 아래의 과정을 거칩니다:
- Python 2.7 바이너리 찾기 시도 → 실패 (EOL로 인한 바이너리 부재)
- 소스 코드 다운로드 → GitHub 에서 Python 2.7.18 소스코드 다운로드
- 의존성 설치 → build-essential, libssl-dev 등 컴파일 도구 설치
- 소스 컴파일 → ./configure, make, make install (가장 시간 소모적)
- 툴 캐시 복사 → 다음 실행을 위한 캐시 저장
위의 모든 과정에서 약 1분 34초가 소요되었고 이를 python2.7 이 설치된 Docker Image 를 사용하도록 개선하여 6초 내외의 시간으로 단축할 수 있었습니다. python2.7 빌드를 위한 docker image 는 최소한의 용량으로 구성된 경량 이미지로 python2.7-slim 을 선택했습니다. slim 버전은 불필요한 빌드 도구, 문서 파일, 테스트 라이브러리 등이 제거되어 이미지 전체 크기를 크게 줄일 수 있습니다. alpine 이미지의 크기가 더 작다는 장점이 있지만 현재 프로젝트에서 자주 사용되는 C 확장 패키지 설치 시 복잡한 종속성 문제와 빌드 실패 가능성이 존재하기 때문에 slim 이미지를 선택했습니다. 실제로 H 프로젝트는 native C 확장 패키지인 psycopg2, cryptography 등을 여럿 사용중이기 때문에 Debian 기반의 패키지 호환성이 좋은 slim 이미지를 선택했습니다.
성능 개선
: Python 빌드 시간 93% 단축
Before: - name: Setup Py2 uses: MatteoH2O1999/setup-python@v2 with: python-version: '2.7' cache: pip After: container: image: python:2.7-slim # 사전 빌드된 Docker 이미지 사용 options: --user root
4. apt upgrade 제거
아래는 기존 ci-test 스크립트에 포함된 코드 중 일부입니다.
# before sudo apt update && sudo apt upgrade -y sudo apt install curl apt-transport-https gnupg2 wget aria2 -y
- 우선 Docker 컨테이너는 기본적으로 root 권한으로 실행되기 때문에 sudo 오버헤드가 불필요하여 명령어에서 모든 sudo 를 제거했습니다.
- 두번째로는 apt 대신 apt-get 으로 명령어를 수정했습니다. apt 명령어는 사용자 친화적 인터페이스를 제공해 진행률 표시 등 기능이 있지만 ci 과정에선 불필요하기 때문에 스크립트 용에 맞게 apt-get 으로 변경했습니다.
- 마지막으로 필요한 패키지만 설치하도록 필수 패키지를 명시했습니다. sudo apt update && sudo apt upgrade -y 명령어는 모든 패키지를 업그레이드를 의도합니다. 이는 ci 라는 일화성 환경에서 보안 패치까지 적용하는 불필요한 작업이며 모든 패키지를 다운받는데 12MB 의 추가 다운로드로 1분 이상 소요되는 점도 문제였습니다. gnupg2 패키지는 PowerShell 저장소 추가용이지만 PowerShell 자체가 불필요하므로 제거했고, apt-transport-https 역시 apt 에서 이미 HTTPS 지원하여 불필요합니다.
# after apt-get update apt-get install -y curl wget aria2 default-mysql-client gcc python-dev libssl-dev zlib1g-dev
- 빌드용: gcc, python-dev (pycrypto 컴파일용)
- 네트워크: curl, wget, aria2
- DB Client: default-mysql-client
- 필수 라이브러리: libssl-dev, zlib1g-dev
마무리
전체 ci-test 결과 차이
기존 ci-test 프로세스
개선된 ci-test 프로세스
글의 제목에선 python2 의 마이그레이션 과정을 담을 것 같았지만, 우선 선행 작업으로 기존 ci 작업이 어떻게 진행되는지 파악이 필요했습니다. 결과적으로 ci-test 과정을 평균 13분에서 5분으로 단축하며 평균 ci 시간을 60% 이상 단축할 수 있었습니다. 단순 시간 절약을 넘어 빠르게 테스트하고 배포할 수 있는 환경을 구축하여 생산성 향상에도 기여할 수 있었습니다. 이번 CI 최적화는 Python 3 마이그레이션을 위한 첫 번째 단계였습니다. 다음 글에서는 Python 2에서 Python 3로의 마이그레이션 과정과 마주한 도전과제들을 다룰 예정입니다.